﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SqlClient;
using System.Drawing;
using System.Collections;
using System.Windows.Forms;
using Asol.Reporting.Support.Green;
using Asol.Reporting.Support.Data;
using Asol.Reporting.Support.Sql;
using Asol.Reporting.Editor.Components;

namespace Asol.Reporting.Editor.Data
{
    #region class SimpleDataTable : Tabulka obsahující data načtená z databáze.
    /// <summary>
    /// SimpleDataTable : Tabulka obsahující data načtená z databáze.
    /// Tabulku lze vytvořit pouze statickou metodou SimpleDataTable.Load(string sqlCommand).
    /// Struktura tabulky (sloupce) se vytvoří podle dat, která vrátí SQL příkaz.
    /// </summary>
    public class SimpleDataTable
    {
        #region Konstrukce
        /// <summary>
        /// Konstruktor je privátní
        /// </summary>
        private SimpleDataTable()
        {
            this._Clear();
        }
        /// <summary>
        /// Vrátí novou prázdnou tabulku
        /// </summary>
        public static SimpleDataTable Empty
        {
            get { return new SimpleDataTable(); }
        }
        /// <summary>
        /// Vrátí tabulku s daty podle daného selectu
        /// </summary>
        /// <param name="sqlCommand">SQL příkaz (SELECT, EXEC)</param>
        /// <returns></returns>
        public static SimpleDataTable Load(string sqlCommand)
        {
            SimpleDataTable table = new SimpleDataTable();
            table._Load(sqlCommand, null, null);
            return table;
        }
        /// <summary>
        /// Vrátí tabulku s daty podle daného selectu
        /// </summary>
        /// <param name="sqlCommand">SQL příkaz (SELECT, EXEC)</param>
        /// <param name="columnFill">Akce, která se provede pro každý column po jeho načtení z databáze. Akce se provádí po doběhnutí SqlDataReaderu, ale před vkládáním řádků s daty do tabulky.</param>
        /// <returns></returns>
        public static SimpleDataTable Load(string sqlCommand, Action<SimpleDataColumn> columnFill)
        {
            SimpleDataTable table = new SimpleDataTable();
            table._Load(sqlCommand, columnFill, null);
            return table;
        }
        /// <summary>
        /// Vrátí tabulku s daty podle daného selectu
        /// </summary>
        /// <param name="sqlCommand">SQL příkaz (SELECT, EXEC)</param>
        /// <param name="parameters">Parametry příkazu</param>
        /// <returns></returns>
        public static SimpleDataTable Load(string sqlCommand, Asol.Reporting.Support.Sql.ParameterCollection parameters)
        {
            SimpleDataTable table = new SimpleDataTable();
            table._Load(sqlCommand, null, parameters);
            return table;
        }
        /// <summary>
        /// Vrátí tabulku s daty podle daného selectu
        /// </summary>
        /// <param name="sqlCommand">SQL příkaz (SELECT, EXEC)</param>
        /// <param name="columnFill">Akce, která se provede pro každý column po jeho načtení z databáze. Akce se provádí po doběhnutí SqlDataReaderu, ale před vkládáním řádků s daty do tabulky.</param>
        /// <param name="parameters">Parametry příkazu</param>
        /// <returns></returns>
        public static SimpleDataTable Load(string sqlCommand, Action<SimpleDataColumn> columnFill, Asol.Reporting.Support.Sql.ParameterCollection parameters)
        {
            SimpleDataTable table = new SimpleDataTable();
            table._Load(sqlCommand, columnFill, parameters);
            return table;
        }
        /// <summary>
        /// Vizualizace
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return "ColumnCount: " + this.ColumnCount.ToString() + "; RowCount: " + this.RowCount.ToString();
        }
        #endregion
        #region Public property a eventy
        /// <summary>
        /// SQL SELECT, který je v tabulce načten
        /// </summary>
        public string SqlCommand { get; private set; }
        /// <summary>
        /// Soupis sloupců. Je indexován podle pořadového čísla sloupce a podle jeho názvu.
        /// Nepřipouští se duplicitní názvy sloupců.
        /// </summary>
        public SimpleDataColumnCollection Columns { get; private set; }
        /// <summary>
        /// Počet sloupců
        /// </summary>
        public int ColumnCount { get { return (this._LoadedColumns != null ? this._LoadedColumns.Count : this.Columns.Count); } }
        /// <summary>
        /// Soupis řádků.
        /// </summary>
        public SimpleDataRowCollection Rows { get; private set; }
        /// <summary>
        /// Počet řádků
        /// </summary>
        public int RowCount { get { return (this._LoadedRows != null ? this._LoadedRows.Count : this.Rows.Count); } }
        /// <summary>
        /// Režim editace DataGridu
        /// </summary>
        public SimpleDataGridEditMode SimpleEditMode
        {
            get
            {
                return (this.HasGrid ? this.DataGrid.SimpleEditMode : this._SimpleEditMode);
            }
            set
            {
                this._SimpleEditMode = value;
                if (this.HasGrid)
                    this.DataGrid.SimpleEditMode = value;
            }
        }
        private SimpleDataGridEditMode _SimpleEditMode;
        /// <summary>
        /// Vrátí ID pro nový řádek.
        /// ID je interní identita řádku, zvenku se s ní nepracuje.
        /// </summary>
        /// <returns></returns>
        public int GetRowId()
        {
            return this._RowId++;
        }
        private int _RowId;
        /// <summary>
        /// Událost volaná před změnou hodnoty. Změnu lze odvolat, lze modifikovat ukládanou hodnotu.
        /// </summary>
        public event SimpleDataValueChangingHandler ValueChanging;
        /// <summary>
        /// Událost volaná po změně hodnoty. Změnu již nelze odvolat.
        /// </summary>
        public event SimpleDataValueChangeHandler ValueChanged;
        /// <summary>
        /// Vyvolá událost ValueChanging.
        /// Proběhne těsně před vložením nové hodnoty do tabulky.
        /// Handler může hodnotu změnit nebo může změnu odmítnout.
        /// </summary>
        internal void OnValueChanging(object sender, SimpleDataValueChangingArgs args)
        {
            if (this.ValueChanging != null)
                this.ValueChanging(sender, args);
            if (this.HasGrid)
                this._DataGrid.OnValueChanging(sender, args);
        }
        /// <summary>
        /// Vyvolá událost ValueChanged.
        /// Proběhne až poté, kdy je do tabulky vložena nová hodnota.
        /// </summary>
        internal void OnValueChange(SimpleDataRow sender, SimpleDataValueChangeArgs args)
        {
            if (this.ValueChanged != null)
                this.ValueChanged(sender, args);
            if (this.HasGrid)
                this._DataGrid.OnValueChange(sender, args);
        }
        #endregion
        #region Načítání dat pomocí DataReaderu
        /// <summary>
        /// Naplní sebe daty podle daného selectu (načte Columns i Rows).
        /// </summary>
        /// <param name="sqlCommand">SQL příkaz (SELECT, EXEC)</param>
        /// <param name="columnFill">Akce, která se provede pro každý column po jeho načtení z databáze. Akce se provádí po doběhnutí SqlDataReaderu, ale před vkládáním řádků s daty do tabulky.</param>
        /// <param name="parameters">Parametry příkazu</param>
        private void _Load(string sqlSelect, Action<SimpleDataColumn> columnFill, Asol.Reporting.Support.Sql.ParameterCollection parameters)
        {
            this._Clear();
            this._LoadedColumns = new List<SimpleDataColumn>();
            this._LoadedRows = new List<SimpleDataRow>();
            this.SqlCommand = sqlSelect;
            Db_Layer.ReadFromDataReader(sqlSelect, this._LoadColumns, this._LoadRow, parameters);
            this.Columns = new SimpleDataColumnCollection(this, this._LoadedColumns);
            this._ColumnsFill(columnFill);
            this._LoadedColumns = null;
            this.Rows = new SimpleDataRowCollection(this, this._LoadedRows);
            this._LoadedRows = null;
        }
        private void _Clear()
        {
            this.SqlCommand = "";
            this.Columns = new SimpleDataColumnCollection(this);
            this.Rows = new SimpleDataRowCollection(this);
            this.CellStyle = SimpleDataStyle.Default;
            this._RowId = 0;
        }
        private void _LoadColumns(SqlDataReader reader)
        {
            for (int columnIndex = 0; columnIndex < reader.FieldCount; columnIndex++)
                this._LoadedColumns.Add(new SimpleDataColumn(this, columnIndex, reader.GetName(columnIndex), reader.GetFieldType(columnIndex)));
        }
        private bool _LoadRow(SqlDataReader reader)
        {
            this._LoadedRows.Add(new SimpleDataRow(this, reader));
            return true;
        }
        private void _ColumnsFill(Action<SimpleDataColumn> columnFill)
        {
            if (columnFill == null) return;
            foreach (SimpleDataColumn column in this.Columns)
                columnFill(column);
        }
        private List<SimpleDataColumn> _LoadedColumns;
        private List<SimpleDataRow> _LoadedRows;
        #endregion
        #region Propojení se SimpleDataGridem
        /// <summary>
        /// Příznak true, pokud k této tabulce existuje vizuální DataGrid.
        /// Pak se automaticky propojují vlastnosti této tabulky do jejího gridu.
        /// </summary>
        public bool HasGrid { get { return (this._DataGrid != null); } }
        /// <summary>
        /// Objekt sloužící k vizualizaci Gridu
        /// </summary>
        public SimpleDataGrid DataGrid
        {
            get { return this._DataGrid; }
            set
            {
                if (this._DataGrid != null)
                    this._DataGridUnlink(this._DataGrid);
                this._DataGrid = value;
                if (this._DataGrid != null)
                    this._DataGridLink(this._DataGrid);
            }
        }
        /// <summary>
        /// Propojí se s daným gridem
        /// </summary>
        /// <param name="dataGrid"></param>
        private void _DataGridLink(SimpleDataGrid dataGrid)
        {
            
        }
        /// <summary>
        /// Odpojí se od daného gridu
        /// </summary>
        /// <param name="dataGrid"></param>
        private void _DataGridUnlink(SimpleDataGrid dataGrid)
        {
            
        }
        private SimpleDataGrid _DataGrid;
        #endregion
        #region Získání dat sloupce, hodnoty z řádku a sloupce
        /// <summary>
        /// Zjistí, zda tato tabulka obsahuje sloupec daného názvu
        /// </summary>
        /// <param name="columnName"></param>
        /// <returns></returns>
        public bool ContainColumn(string columnName)
        {
            string key = columnName.ToLower();
            return this.Columns.ContainsColumn(key);
        }
        /// <summary>
        /// Zjistí, zda tato tabulka obsahuje sloupec daného indexu
        /// </summary>
        /// <param name="columnId"></param>
        /// <returns></returns>
        public bool ContainColumn(int columnId)
        {
            return this.Columns.ContainsColumn(columnId);
        }
        /// <summary>
        /// Vrátí sloupec podle jeho názvu.
        /// Pokud neexistuje, vyhodí chybu.
        /// </summary>
        /// <param name="columnName"></param>
        /// <returns></returns>
        public SimpleDataColumn GetColumn(string columnName)
        {
            string key = columnName.ToLower();
            if (!this.Columns.ContainsColumn(key))
                throw new FrxException("Column \"" + columnName + "\" does not exists in SimpleTable.");
            return this.Columns[key];
        }
        /// <summary>
        /// Vrátí sloupec podle jeho indexu.
        /// Pokud neexistuje, vyhodí chybu.
        /// </summary>
        /// <param name="columnId"></param>
        /// <returns></returns>
        public SimpleDataColumn GetColumn(int columnId)
        {
            if (!this.Columns.ContainsColumn(columnId))
                throw new FrxException("Column with index " + columnId.ToString() + " does not exists in SimpleTable.");
            return this.Columns[columnId];
        }
        /// <summary>
        /// Vrátí hodnotu z daného řádku a sloupce
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="rowIndex"></param>
        /// <param name="columnName"></param>
        /// <returns></returns>
        public T Get<T>(int rowIndex, string columnName)
        {
            return this.Get<T>(rowIndex, GetColumn(columnName).ColumnIndex);
        }
        /// <summary>
        /// Vrátí hodnotu z daného řádku a sloupce
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="rowIndex"></param>
        /// <param name="columnIndex"></param>
        /// <returns></returns>
        public T Get<T>(int rowIndex, int columnIndex)
        {
            if (rowIndex < 0 || rowIndex >= this.RowCount)
                throw new FrxException("Row index " + rowIndex.ToString() + " is out of range, RowCount = " + this.RowCount.ToString());
            return this.Rows[rowIndex].Get<T>(columnIndex);
        }
        #endregion
        #region Vizuální styl
        /// <summary>
        /// Výchozí styl buněk.
        /// </summary>
        public SimpleDataStyle CellStyle { get; set; }
        #endregion
        #region Služby
        /// <summary>
        /// Vrátí režim slučování buněk pro danou buňku.
        /// Podporuje možnost SimpleDataColumn.ShowOnlyDifferentValues.
        /// </summary>
        /// <param name="rowIndex"></param>
        /// <param name="columnIndex"></param>
        /// <returns></returns>
        public SimpleCellJoinMode DetectCellJoin(int rowIndex, int columnIndex)
        {
            if (rowIndex >= 0 && rowIndex < this.RowCount && columnIndex >= 0 && columnIndex < this.ColumnCount)
            {
                SimpleDataColumn column = this.Columns[columnIndex];      // V tomto sloupci určujeme JoinMode
                if (column.ShowOnlyDifferentValues)
                {
                    int detectInColumn = column.DetectDifferentValuesInColumnIndex;
                    bool isFirst = (rowIndex == 0 || this._DetectCellJoinDifferentValue(rowIndex, rowIndex - 1, detectInColumn));
                    bool isLast = (rowIndex == (this.RowCount - 1) || this._DetectCellJoinDifferentValue(rowIndex, rowIndex + 1, detectInColumn));
                    if (isFirst && isLast)
                        return SimpleCellJoinMode.SingleCell;
                    if (isFirst)
                        return SimpleCellJoinMode.FirstCell;
                    if (isLast)
                        return SimpleCellJoinMode.LastCell;
                    return SimpleCellJoinMode.InnerCell;
                }
            }
            return SimpleCellJoinMode.None;
        }
        /// <summary>
        /// Vrací true, pokud data v daných řádcích daného sloupce jsou ODLIŠNÁ.
        /// </summary>
        /// <param name="rowIndex1"></param>
        /// <param name="rowIndex2"></param>
        /// <param name="columnIndex"></param>
        /// <returns></returns>
        private bool _DetectCellJoinDifferentValue(int rowIndex1, int rowIndex2, int columnIndex)
        {
            SimpleDataColumn column = this.Columns[columnIndex];
            string value1 = column.Formatter.GetDisplayValue(this.Rows[rowIndex1][columnIndex]);
            string value2 = column.Formatter.GetDisplayValue(this.Rows[rowIndex2][columnIndex]);
            return (!String.Equals(value1, value2, StringComparison.InvariantCulture));
        }
        #endregion
    }
    /// <summary>
    /// Režim slučování buněk
    /// </summary>
    public enum SimpleCellJoinMode
    {
        /// <summary>
        /// V tomto sloupci se buňky neslučují
        /// </summary>
        None = 0,
        /// <summary>
        /// V tomto sloupci se buňky slučují, ale tato buňka je samotná
        /// </summary>
        SingleCell,
        /// <summary>
        /// První buňka v řadě
        /// </summary>
        FirstCell,
        /// <summary>
        /// Vnitřní buňka (ani první, ani poslední)
        /// </summary>
        InnerCell,
        /// <summary>
        /// Poslední buňka v řadě
        /// </summary>
        LastCell
    }
    #endregion
    #region class SimpleDataColumnCollection : kolekce sloupců
    /// <summary>
    /// Kolekce sloupců
    /// </summary>
    public class SimpleDataColumnCollection : IEnumerable<SimpleDataColumn>
    {
        /// <summary>
        /// Konstruktor
        /// </summary>
        public SimpleDataColumnCollection(SimpleDataTable table)
        {
            this.Table = table;
            this.Columns = new CollectIdx<SimpleDataColumn>();
        }
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="columns"></param>
        public SimpleDataColumnCollection(SimpleDataTable table, IEnumerable<SimpleDataColumn> columns)
        {
            this.Table = table;
            this.Columns = new CollectIdx<SimpleDataColumn>();
            if (columns != null)
                this.Columns.AddRange(columns);
        }
        /// <summary>
        /// Vizualizace
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return "ColumnCollection of " + this.Count.ToString() + " columns";
        }
        private CollectIdx<SimpleDataColumn> Columns;
        private IEnumerable<SimpleDataColumn> IEnumerableColumns { get { return (IEnumerable<SimpleDataColumn>)this.Columns; } }
        #region Public property a metody
        /// <summary>
        /// Tabulka, do níž kolekce patří
        /// </summary>
        public SimpleDataTable Table { get; private set; }
        /// <summary>
        /// Počet sloupců v kolekci
        /// </summary>
        public int Count { get { return this.Columns.Count; } }
        /// <summary>
        /// Vrátí sloupec podle jeho indexu
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public SimpleDataColumn this[int index] { get { return this.Columns[index]; } }
        /// <summary>
        /// Vrátí sloupec podle jeho názvu
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public SimpleDataColumn this[string key] { get { return this.Columns[key]; } }
        /// <summary>
        /// Vrátí informaci, zda existuje sloupec daného indexu
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public bool ContainsColumn(int index) { return this.Columns.ContainsKey(index); }
        /// <summary>
        /// Vrátí informaci, zda existuje sloupec daného názvu
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public bool ContainsColumn(string key) { return this.Columns.ContainsKey(key); }
        /// <summary>
        /// Metoda smaže obsah této kolekce sloupců
        /// </summary>
        public void Clear()
        {
            this.Columns.Clear();
            if (this.Table.HasGrid)
                this.Table.DataGrid.ClearColumnsInternal();
        }
        #endregion
        #region Add Column
        /// <summary>
        /// Přidá sloupec
        /// </summary>
        /// <param name="column"></param>
        public SimpleDataColumn Add(string columnName, Type dataType)
        {
            SimpleDataColumn column = new SimpleDataColumn(this.Table, this.Count, columnName, dataType);
            this.Columns.Add(column);
            return column;
        }
        /// <summary>
        /// Přidá sloupec
        /// </summary>
        /// <param name="column"></param>
        public SimpleDataColumn Add(string columnName, string headerText, Type dataType)
        {
            SimpleDataColumn column = new SimpleDataColumn(this.Table, this.Count, columnName, dataType);
            column.HeaderText = headerText;
            this.Columns.Add(column);
            return column;
        }
        /// <summary>
        /// Přidá sloupec
        /// </summary>
        /// <param name="column"></param>
        public SimpleDataColumn Add(string columnName, Type dataType, EditStyleInfo editStyle)
        {
            SimpleDataColumn column = new SimpleDataColumn(this.Table, this.Count, columnName, dataType);
            column.EditStyle = editStyle;
            this.Columns.Add(column);
            return column;
        }
        /// <summary>
        /// Přidá sloupec
        /// </summary>
        /// <param name="column"></param>
        public SimpleDataColumn Add(string columnName, string headerText, Type dataType, EditStyleInfo editStyle)
        {
            SimpleDataColumn column = new SimpleDataColumn(this.Table, this.Count, columnName, dataType);
            column.HeaderText = headerText;
            column.EditStyle = editStyle;
            this.Columns.Add(column);
            return column;
        }
        /// <summary>
        /// Přidá sloupec
        /// </summary>
        /// <param name="column"></param>
        public SimpleDataColumn Add(string columnName, Type dataType, string editStyleValues)
        {
            SimpleDataColumn column = new SimpleDataColumn(this.Table, this.Count, columnName, dataType);
            column.EditStyleValues = editStyleValues;
            this.Columns.Add(column);
            return column;
        }
        /// <summary>
        /// Přidá sloupec
        /// </summary>
        /// <param name="column"></param>
        public SimpleDataColumn Add(string columnName, string headerText, Type dataType, string editStyleValues)
        {
            SimpleDataColumn column = new SimpleDataColumn(this.Table, this.Count, columnName, dataType);
            column.HeaderText = headerText;
            column.EditStyleValues = editStyleValues;
            this.Columns.Add(column);
            return column;
        }
        #endregion
        #region IEnumerable
        IEnumerator<SimpleDataColumn> IEnumerable<SimpleDataColumn>.GetEnumerator() { return this.IEnumerableColumns.GetEnumerator(); }
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return this.IEnumerableColumns.GetEnumerator(); }
        #endregion
    }
    #endregion
    #region class SimpleDataColumn : Informace o jednom sloupci
    /// <summary>
    /// SimpleDataColumn : Informace o jednom sloupci
    /// </summary>
    public class SimpleDataColumn : IIdKey
    {
        #region Konstrukce
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="table"></param>
        /// <param name="columnIndex"></param>
        /// <param name="columnName"></param>
        /// <param name="dataType"></param>
        public SimpleDataColumn(SimpleDataTable table, int columnIndex, string columnName, Type dataType)
        {
            this.Table = table;
            this.ColumnIndex = columnIndex;
            this.ColumnName = columnName;
            this.DataType = dataType;
            this.ColumnKey = columnName.Trim().ToLower();
            this.ColumnWidth = null;
            this.FillWeight = null;
            this.AutoSizeMode = DataGridViewAutoSizeColumnMode.NotSet;
            this.GridVisible = true;
        }
        /// <summary>
        /// Vizualizace
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return "[" + this.ColumnIndex + "] " + this.ColumnName;
        }
        #endregion
        #region Public property
        /// <summary>
        /// Reference na tabulku, k níž sloupec náleží
        /// </summary>
        public SimpleDataTable Table { get; private set; }
        /// <summary>
        /// Index sloupce
        /// </summary>
        public int ColumnIndex { get; private set; }
        /// <summary>
        /// Název sloupce, tak jak byl načten z databáze (může mít malé i velké znaky)
        /// </summary>
        public string ColumnName { get; private set; }
        /// <summary>
        /// Typ dat načtený z readeru. Obsahuje Type, který nereflektuje NULL stavy, tedy typicky Int32.
        /// </summary>
        public Type DataType { get; private set; }
        /// <summary>
        /// Editace dat v řádku je povolena?
        /// Zde může být null, pak se převezme z tabulky.
        /// </summary>
        public bool? Enabled { get; set; }
        /// <summary>
        /// Název sloupce jako key (tj.vždy lower)
        /// </summary>
        protected string ColumnKey { get; private set; }
        /// <summary>
        /// Text, který se objevuje v záhlaví sloupce.
        /// </summary>
        public string HeaderText
        {
            get
            {
                string text = this._HeaderText;
                if (text != null)
                    return text;

                text = this.ColumnName;
                if (this.Attribute != null)
                    text = this.Attribute.NazevCol;
                if (this.HasRelation)
                    text = this.Relation.Nazev + " - " + text;
                return text;
            }
            set { this._HeaderText = value; }
        }
        private string _HeaderText;
        /// <summary>
        /// Šířka sloupce
        /// </summary>
        public int? ColumnWidth { get; set; }
        /// <summary>
        /// Tooltip zobrazovaný na hlavičce sloupce
        /// </summary>
        public string ToolTipText { get; set; }
        /// <summary>
        /// Poměrná váha šířky sloupce
        /// </summary>
        public float? FillWeight { get; set; }
        /// <summary>
        /// Zobrazovat text ve skupinách, jen pokud řádky nad sebou obsahují odlišné hodnoty (grupování textu ve sloupci).
        /// Nastavením na true 
        /// </summary>
        public bool ShowOnlyDifferentValues { get; set; }
        /// <summary>
        /// Zobrazovat text ve skupinách, jen pokud řádky nad sebou obsahují odlišné hodnoty v tomto sloupci.
        /// Využití: pokud aktuální sloupec (this) reprezentuje název, ale chceme oddělovat skupiny řádků podle například čísla subjektu,
        /// pak se nelze spoléhat na textovou reprezentaci názvu (mohou jít po sobě dva různé subjekty se shodným názvem...)
        /// </summary>
        public SimpleDataColumn ShowOnlyDifferentValuesByColumn
        {
            get { return this._ShowOnlyDifferentValuesByColumn; }
            set
            {
                this._ShowOnlyDifferentValuesByColumn = value;
                if (value != null)
                    this.ShowOnlyDifferentValues = true;
            }
        }
        private SimpleDataColumn _ShowOnlyDifferentValuesByColumn;
        /// <summary>
        /// Index sloupce, který slouží jako zdroj hodnot pro detekci změny textu (zobrazení textu ve skupinách).
        /// -1 pokud není nastaveno zobrazovat ve skupinách (this.ShowOnlyDifferentValues je false).
        /// </summary>
        public int DetectDifferentValuesInColumnIndex
        {
            get
            {
                if (!this.ShowOnlyDifferentValues) return -1;
                if (this._ShowOnlyDifferentValuesByColumn == null) return this.ColumnIndex;
                return this._ShowOnlyDifferentValuesByColumn.ColumnIndex;
            }
        }
        /// <summary>
        /// Režim šířky sloupce
        /// </summary>
        public DataGridViewAutoSizeColumnMode AutoSizeMode { get; set; }
        /// <summary>
        /// Viditelnost v Gridu
        /// </summary>
        public bool GridVisible { get; set; }
        #endregion
        #region Popis dat ve sloupci (atribut, vztah, RenderAs, Formatter, Style)
        /// <summary>
        /// Atribut, uložený v tomto sloupci.
        /// Vložení atributu (který není null) vloží jeho editační styl do this.EditStyleList nebo EditStyleMask.
        /// </summary>
        public NorisAttributesCls Attribute
        {
            get { return this._Attribute; }
            set
            {
                this._Attribute = value;
                this.Formatter.EditStyle = (value == null ? null : value.EditStyle);
                this._ExplicitRenderAs = SimpleDataRenderAsType.NotDefined;
            }
        }
        /// <summary>
        /// Vztah, uložený v tomto sloupci.
        /// Vztah určuje, že obsah sloupce se edituje a zobrazuje jinak.
        /// Vztah může existovat společně s atributem, protože atribut popisuje reálný sloupec (záhlaví), kdežto vztah popisuje zobrazení a chování.
        /// </summary>
        public RelationInfo Relation
        {
            get { return this._Relation; }
            set { this._Relation = value; }
        }
        /// <summary>
        /// Editační styl sloupce, načtený z Greenu nebo ručně vytvořený
        /// </summary>
        public EditStyleInfo EditStyle
        {
            get { return this.Formatter.EditStyle; }
            set { this.Formatter.EditStyle = value; }
        }
        /// <summary>
        /// Nabídka textů editačního stylu.
        /// </summary>
        public IEditStyleList EditStyleList
        {
            get { return this.Formatter.EditStyleList; }
            set { this.Formatter.EditStyleList = value; }
        }
        /// <summary>
        /// Položky nabídky editačního stylu.
        /// Ve formě stringu jsou obsaženy pouze DisplayText, které se stávají současně i Value (=do tabulky se ukládá zobrazovaný text).
        /// Jde tedy o valuovaný atribut.
        /// String může obsahovat oddělovače položek Cr, Lf, Tab, Středník, Čárka (zde uvedené pořadí deklaruje prioritu).
        /// Jako skutečně použitý oddělovač se bere pouze jeden, ten s nejvyšší prioritou, který je ve stringu přítomen.
        /// </summary>
        public string EditStyleValues
        {
            get { return this.Formatter.EditStyleValues; }
            set { this.Formatter.EditStyleValues = value; }
        }
        /// <summary>
        /// Maska editace
        /// </summary>
        public string EditMask
        {
            get { return this.Formatter.EditMask; }
            set { this.Formatter.EditMask = value; }
        }
        /// <summary>
        /// Příznak (true), pokud tento sloupec vyjadřuje vztah (má uveden Relation) = jeho zobrazování se provádí jako vztah
        /// </summary>
        public bool HasRelation { get { return (this._Relation != null); } }
        /// <summary>
        /// Styl, jakým se mají renderovat data tohoto sloupce (druh Controlu).
        /// Pokud je 
        /// </summary>
        public SimpleDataRenderAsType RenderAs
        {
            get
            {
                if (this._ExplicitRenderAs != SimpleDataRenderAsType.NotDefined)
                    return this._ExplicitRenderAs;
                if (this.DataType.Equals(typeof(object[])))
                    return SimpleDataRenderAsType.Image;
                else
                    return this.Formatter.RenderAs;
            }
        }
        /// <summary>
        /// Explicitně požadovaný druh renderování.
        /// Ovlivňuje hodnotu this.RenderAs.
        /// Implicitně je NotDefined, pak o renderování rozhoduje typ dat (this.DataType: pokud je object[], pak se renderuje jako Image, jinak podle this.Formatter.RenderAs;.
        /// </summary>
        public SimpleDataRenderAsType ExplicitRenderAs { get { return this._ExplicitRenderAs; } set { this._ExplicitRenderAs = value; } }
        /// <summary>
        /// Objekt, který zabezpečuje formátování hodnoty do vizuální formy a nazpět.
        /// Objekt je v rámci Columnu autoinicializační.
        /// </summary>
        public SimpleDataFormatter Formatter
        {
            get
            {
                if (this._Formatter == null)
                    this._Formatter = new SimpleDataFormatter();
                return this._Formatter;
            }
            set { this._Formatter = value; }
        }
        /// <summary>
        /// Výchozí styl buněk.
        /// Zde může být null, pak se převezme z tabulky.
        /// </summary>
        public SimpleDataStyle CellStyle { get; set; }
        private NorisAttributesCls _Attribute;
        private RelationInfo _Relation;
        private SimpleDataRenderAsType _ExplicitRenderAs = SimpleDataRenderAsType.NotDefined;
        private SimpleDataFormatter _Formatter;
        #endregion
        #region Members of interface IIdKey
        int IIdKey.Id { get { return this.ColumnIndex; } }
        string IIdKey.Key { get { return this.ColumnKey; } }
        #endregion
    }
    /// <summary>
    /// Jak se mají renderovat data konkrétního sloupce
    /// </summary>
    public enum SimpleDataRenderAsType
    {
        NotDefined = 0,
        NoRender,
        TextBox,
        ComboBox,
        CheckBox,
        Image
    }
    #endregion
    #region class SimpleDataRowCollection : kolekce řádků
    /// <summary>
    /// SimpleDataRowCollection : kolekce řádků
    /// </summary>
    public class SimpleDataRowCollection : IEnumerable<SimpleDataRow>
    {
        #region Konstrukce
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="table">Tabulka, která je vlastníkem řádku</param>
        public SimpleDataRowCollection(SimpleDataTable table)
        {
            this.Table = table;
            this.Rows = new List<SimpleDataRow>();
        }
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="table">Tabulka, která je vlastníkem řádku</param>
        /// <param name="rows"></param>
        public SimpleDataRowCollection(SimpleDataTable table, IEnumerable<SimpleDataRow> rows)
        {
            this.Table = table;
            this.Rows = new List<SimpleDataRow>();
            if (rows != null)
                this.Rows.AddRange(rows);
        }
        /// <summary>
        /// Vizualizace
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return "RowCollection of " + this.Count.ToString() + " rows";
        }
        #endregion
        #region Property
        /// <summary>
        /// Řádky
        /// </summary>
        private List<SimpleDataRow> Rows;
        /// <summary>
        /// Tabulka, do níž kolekce patří
        /// </summary>
        public SimpleDataTable Table { get; private set; }
        /// <summary>
        /// Počet řádků v kolekci
        /// </summary>
        public int Count { get { return this.Rows.Count; } }
        /// <summary>
        /// Vrátí řádek podle jeho indexu
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public SimpleDataRow this[int index] { get { return this.Rows[index]; } }
        #endregion
        #region Add, Get, Remove Row
        /// <summary>
        /// Přidá řádek
        /// </summary>
        /// <param name="values"></param>
        /// <returns></returns>
        public SimpleDataRow Add(params object[] values)
        {
            SimpleDataRow row = new SimpleDataRow(this.Table, values);
            this._AddRow(row);
            return row;
        }
        /// <summary>
        /// Přidá daný řádek do this a do Gridu.
        /// </summary>
        /// <param name="row"></param>
        private void _AddRow(SimpleDataRow row)
        {
            this.Rows.Add(row);
            if (this.Table.HasGrid)
                this.Table.DataGrid.AddDataRowInternal(row);
        }
        /// <summary>
        /// Vrátí soupis řádků odpovídající požadovaným indexům.
        /// Pokud bude některý index zadán vícekrát, bude vrácen jen jedenkrát.
        /// Pokud některý zadaný index bude mimo rozsah, dojde k chybě.
        /// </summary>
        /// <param name="rowIndexes"></param>
        /// <returns></returns>
        public IEnumerable<SimpleDataRow> Get(IEnumerable<int> rowIndexes)
        {
            Dictionary<int, SimpleDataRow> rows = new Dictionary<int, SimpleDataRow>();
            if (rowIndexes != null)
            {
                foreach (int rowIndex in rowIndexes)
                {
                    if (!rows.ContainsKey(rowIndex))
                        rows.Add(rowIndex, this[rowIndex]);
                }
            }
            return rows.Values.ToArray();
        }
        /// <summary>
        /// Odebere řádek daný jeho indexem
        /// </summary>
        /// <param name="rowIndex"></param>
        public void Remove(int rowIndex)
        {
            this.Remove(this[rowIndex]);
        }
        /// <summary>
        /// Odebere řádky dané seznamem jejich indexů
        /// </summary>
        /// <param name="rowIndexes"></param>
        public void Remove(IEnumerable<int> rowIndexes)
        {
            this.Remove(this.Get(rowIndexes));
        }
        /// <summary>
        /// Odebere dané řádky
        /// </summary>
        /// <param name="rows"></param>
        public void Remove(IEnumerable<SimpleDataRow> rows)
        {
            if (rows == null) return;
            foreach (SimpleDataRow row in rows)
                this.Remove(row);
        }
        /// <summary>
        /// Odebere daný řádek
        /// </summary>
        /// <param name="row"></param>
        public void Remove(SimpleDataRow row)
        {
            this.Rows.RemoveAll(r => Object.ReferenceEquals(r, row));
            if (this.Table.HasGrid)
                this.Table.DataGrid.RemoveDataRowInternal(row);
        }
        /// <summary>
        /// Smaže všechny řádky z dat i z navázaného Gridu.
        /// </summary>
        public void Clear()
        {
            this.Rows.Clear();
            if (this.Table.HasGrid)
                this.Table.DataGrid.ClearRowsInternal();
        }
        /// <summary>
        /// Vrací index daného prvku v této kolekci.
        /// Pokud daný řádek v této kolekci není, vrací -1.
        /// </summary>
        /// <param name="simpleDataRow"></param>
        /// <returns></returns>
        public int FindIndexOfRow(SimpleDataRow row)
        {
            return this.Rows.FindIndex(r => Object.ReferenceEquals(r, row));
        }
        #endregion
        #region Import Rows
        /// <summary>
        /// Importuje sadu řádků
        /// </summary>
        /// <param name="rows"></param>
        public void Import(SimpleDataRowCollection rows)
        {
            this.Import(rows, false);
        }
        /// <summary>
        /// Importuje sadu řádků
        /// </summary>
        /// <param name="rows">Vstupující řádky. Do tabulky se přidají jejich DeepClone, nikoli objekty předané do této metody.</param>
        /// <param name="append">Append: true = přidat nová data ke stávajícím řádkům / false = nepřidávat, nejprve smazat řádky.</param>
        public void Import(SimpleDataRowCollection rows, bool append)
        {
            if (!append)
                this.Clear();
            
            foreach (SimpleDataRow r in rows)
            {
                SimpleDataRow row = new SimpleDataRow(this.Table, r);
                this._AddRow(row);
            }
        }
        #endregion
        #region IEnumerable
        IEnumerator<SimpleDataRow> IEnumerable<SimpleDataRow>.GetEnumerator() { return this.Rows.GetEnumerator(); }
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return this.Rows.GetEnumerator(); }
        #endregion
    }
    #endregion
    #region class SimpleDataRow : Data jednoho řádku
    /// <summary>
    /// SimpleDataRow : Data jednoho řádku
    /// </summary>
    public class SimpleDataRow
    {
        #region Konstrukce
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="table"></param>
        /// <param name="reader"></param>
        internal SimpleDataRow(SimpleDataTable table, SqlDataReader reader)
        {
            int columnCount = table.ColumnCount;
            this.ParentTable = table;
            this.NestedTables = null;
            this.Id = table.GetRowId();
            this.Data = new object[columnCount];
            reader.GetValues(this.Data);
            for (int columnIndex = 0; columnIndex < columnCount; columnIndex++)
            {
                if (reader.IsDBNull(columnIndex))
                    this.Data[columnIndex] = null;
            }
        }

        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="table"></param>
        /// <param name="values"></param>
        internal SimpleDataRow(SimpleDataTable table, SimpleDataRow original)
        {
            int columnCount = table.ColumnCount;
            this.ParentTable = table;
            this.NestedTables = null;
            this.Id = table.GetRowId();
            this.Data = new object[columnCount];
            int valuesCount = (original == null || original.Data == null ? -1 : original.Data.Length);

            // Přenesu Values:
            for (int columnIndex = 0; columnIndex < columnCount; columnIndex++)
            {
                if (columnIndex < valuesCount)
                    this.Data[columnIndex] = original.Data[columnIndex];
            }

            // Přenesu i Cells:
            this._CellCollection = null;
            if (original != null && original._CellCollection != null)
                this._CellCollection = original._CellCollection.Clone(this);
        }
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="table"></param>
        /// <param name="values"></param>
        internal SimpleDataRow(SimpleDataTable table, params object[] values)
        {
            int columnCount = table.ColumnCount;
            this.ParentTable = table;
            this.NestedTables = null;
            this.Id = table.GetRowId();
            this.Data = new object[columnCount];
            int valuesCount = (values == null ? -1 : values.Length);
            for (int columnIndex = 0; columnIndex < columnCount; columnIndex++)
            {
                if (columnIndex < valuesCount)
                    this.Data[columnIndex] = values[columnIndex];
            }
        }
        #endregion
        #region Public property
        /// <summary>
        /// Interní identita řádku
        /// </summary>
        public int Id { get; private set; }
        /// <summary>
        /// Tabulka, která vlastní tento řádek
        /// </summary>
        public SimpleDataTable ParentTable { get; private set; }
        /// <summary>
        /// Podřízené tabulky
        /// </summary>
        public List<SimpleDataTable> NestedTables { get; set; }
        /// <summary>
        /// Vrátí všechny hodnoty řádku v jednom objektu
        /// </summary>
        /// <returns></returns>
        public object[] GetValues()
        {
            return this.Data;
        }
        /// <summary>
        /// Počet hodnot řádku
        /// </summary>
        public int ValueCount { get { return this.Data.Length; } }
        /// <summary>
        /// Interní ostré úložiště hodnot řádku
        /// </summary>
        protected object[] Data;
        /// <summary>
        /// Editace dat v řádku je povolena?
        /// Zde může být null, pak se převezme ze sloupce nebo tabulky.
        /// </summary>
        public bool? Enabled { get; set; }
        /// <summary>
        /// Index tohoto řádku v kolekci řádků
        /// </summary>
        public int RowIndex { get { return (this.ParentTable == null ? -1 : this.ParentTable.Rows.FindIndexOfRow(this)); } }
        #endregion
        #region Cells[] = SimpleDataCellCollection
        /// <summary>
        /// Přístup k vlastnostem jednotlivých buněk.
        /// K jednotlivým buňkám se přistupuje přes indexer: row.Cells[columnIndex].ToolTipText = "text", atd.
        /// </summary>
        public SimpleDataCellCollection Cells
        {
            get
            {
                if (this._CellCollection == null)
                    this._CellCollection = new SimpleDataCellCollection(this);
                return this._CellCollection;
            }
        }
        private SimpleDataCellCollection _CellCollection;
        #endregion
        #region CellStyle - podpora pro vykreslení
        /// <summary>
        /// Vrátí styl pro kreslení dané buňky.
        /// </summary>
        /// <param name="columnIndex"></param>
        /// <param name="hotMouse"></param>
        /// <param name="selectedCell"></param>
        /// <param name="activeRow"></param>
        /// <returns></returns>
        public SimpleDataStyleOne GetStyle(int columnIndex, bool hotMouse, bool selectedCell, bool activeRow)
        {
            bool enabled = this.IsEnabled(columnIndex);
            SimpleDataStyleState state = GetState(!enabled, hotMouse, selectedCell, activeRow);
            SimpleDataStyleOne style = null;
            if (this.Cells.ContainData(columnIndex) && this.Cells[columnIndex].CellStyle != null)
                style = this.Cells[columnIndex].CellStyle[state];         // Pro buňku [columnIndex] přečtu její definici stylu pro daný stav. Styl může být null.
            if (style == null && this.CellStyle != null)
                style = this.CellStyle[state];
            if (style == null && this.ParentTable.Columns[columnIndex].CellStyle != null)
                style = this.ParentTable.Columns[columnIndex].CellStyle[state];
            if (style == null)
                style = this.ParentTable.CellStyle[state];
            return style;
        }
        /// <summary>
        /// Vrátí true, pokud je možno buňku v daném sloupci editovat
        /// </summary>
        /// <param name="columnIndex"></param>
        /// <returns></returns>
        public bool IsEnabled(int columnIndex)
        {
            bool? enabled = null;
            if (this.Cells.ContainData(columnIndex))
                enabled = this.Cells[columnIndex].Enabled;
            if (!enabled.HasValue)
                enabled = this.Enabled;
            if (!enabled.HasValue)
                enabled = this.ParentTable.Columns[columnIndex].Enabled;
            if (!enabled.HasValue)
                enabled = (this.ParentTable.SimpleEditMode == SimpleDataGridEditMode.EditContent || this.ParentTable.SimpleEditMode == SimpleDataGridEditMode.EditContentAndRows);
            return enabled.Value;
        }
        /// <summary>
        /// Vrátí stav prvku pro dané příznaky
        /// </summary>
        /// <param name="disabled"></param>
        /// <param name="hotMouse"></param>
        /// <param name="selectedCell"></param>
        /// <param name="activeRow"></param>
        /// <returns></returns>
        public static SimpleDataStyleState GetState(bool disabled, bool hotMouse, bool selectedCell, bool activeRow)
        {
            SimpleDataStyleState state = (SimpleDataStyleState)
                (disabled ? SimpleDataStyleState.Disabled : SimpleDataStyleState.Standard) |
                (hotMouse ? SimpleDataStyleState.HotMouse : SimpleDataStyleState.Standard) |
                (selectedCell ? SimpleDataStyleState.Selected : SimpleDataStyleState.Standard) |
                (activeRow ? SimpleDataStyleState.ActiveRow : SimpleDataStyleState.Standard);
            return state;
        }
        /// <summary>
        /// Výchozí styl buněk.
        /// Zde může být null, pak se převezme ze sloupce nebo tabulky.
        /// </summary>
        public SimpleDataStyle CellStyle { get; set; }
        #endregion
        #region Value
        /// <summary>
        /// Vrací obsah daného sloupce z tohoto řádku, pro daný název sloupce
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="columnName"></param>
        /// <returns></returns>
        public T Get<T>(string columnName)
        {
            return this.Get<T>(this.ParentTable.GetColumn(columnName).ColumnIndex);
        }
        /// <summary>
        /// Vrací obsah daného sloupce z tohoto řádku, pro daný index sloupce
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="columnIndex"></param>
        /// <returns></returns>
        public T Get<T>(int columnIndex)
        {
            if (columnIndex < 0 || columnIndex >= this.ValueCount)
                throw new FrxException("Column index " + Data.ToString() + " is out of range, ValueCount = " + this.ValueCount.ToString());

            object value = this.Data[columnIndex];

            if (value == null) return default(T);
            if (value is T) return (T)value;
            if (System.Convert.IsDBNull(value)) return default(T);

            // Konverze:
            if (typeof(bool).Equals(typeof(T)))
                return (T)((object)System.Convert.ToBoolean(value));
            if (typeof(byte).Equals(typeof(T)))
                return (T)((object)System.Convert.ToByte(value));
            if (typeof(DateTime).Equals(typeof(T)))
                return (T)((object)System.Convert.ToDateTime(value));
            if (typeof(decimal).Equals(typeof(T)))
                return (T)((object)System.Convert.ToDecimal(value));
            if (typeof(double).Equals(typeof(T)))
                return (T)((object)System.Convert.ToDouble(value));
            if (typeof(char).Equals(typeof(T)))
                return (T)((object)System.Convert.ToChar(value));
            if (typeof(Int16).Equals(typeof(T)))
                return (T)((object)System.Convert.ToInt16(value));
            if (typeof(Int32).Equals(typeof(T)))
                return (T)((object)System.Convert.ToInt32(value));
            if (typeof(Int64).Equals(typeof(T)))
                return (T)((object)System.Convert.ToInt64(value));
            if (typeof(sbyte).Equals(typeof(T)))
                return (T)((object)System.Convert.ToSByte(value));
            if (typeof(Single).Equals(typeof(T)))
                return (T)((object)System.Convert.ToSingle(value));
            if (typeof(string).Equals(typeof(T)))
                return (T)((object)System.Convert.ToString(value));
            if (typeof(UInt16).Equals(typeof(T)))
                return (T)((object)System.Convert.ToUInt16(value));
            if (typeof(UInt32).Equals(typeof(T)))
                return (T)((object)System.Convert.ToUInt32(value));
            if (typeof(UInt64).Equals(typeof(T)))
                return (T)((object)System.Convert.ToUInt64(value));

            if (typeof(byte[]).Equals(typeof(T)))
                return (T)(value);
            if (typeof(System.Drawing.Image).Equals(typeof(T)))
                return (T)_GetAsImage(value);

            throw new FrxException("Unable to convert type " + this.ParentTable.GetColumn(columnIndex).DataType.Name + " to type " + typeof(T).Name + ", in SimpleTable.");
        }
        /// <summary>
        /// Hodnota v daném sloupci
        /// </summary>
        /// <param name="columnIndex"></param>
        /// <returns></returns>
        public object this[int columnIndex]
        {
            get
            {
                if (columnIndex < 0 || columnIndex >= this.ValueCount)
                    throw new FrxException("Column index " + Data.ToString() + " is out of range, ValueCount = " + this.ValueCount.ToString());
                return this.Data[columnIndex];
            }
            set
            {
                this._SetValue(value, columnIndex);
            }
        }
        /// <summary>
        /// Hodnota v daném sloupci
        /// </summary>
        /// <param name="columnName"></param>
        /// <returns></returns>
        public object this[string columnName]
        {
            get
            {
                int columnIndex = this.ParentTable.GetColumn(columnName).ColumnIndex;
                if (columnIndex < 0 || columnIndex >= this.ValueCount)
                    throw new FrxException("Column index " + Data.ToString() + " is out of range, ValueCount = " + this.ValueCount.ToString());
                return this.Data[columnIndex];
            }
            set
            {
                int columnIndex = this.ParentTable.GetColumn(columnName).ColumnIndex;
                this._SetValue(value, columnIndex);
            }
        }
        /// <summary>
        /// Vloží danou hodnotu do aného sloupce tohoto řádku.
        /// </summary>
        /// <param name="value"></param>
        /// <param name="columnIndex"></param>
        private void _SetValue(object value, int columnIndex)
        {
            if (columnIndex < 0 || columnIndex >= this.ValueCount)
                throw new FrxException("Column index " + Data.ToString() + " is out of range, ValueCount = " + this.ValueCount.ToString());
            SimpleDataValueChangingArgs args1 = new SimpleDataValueChangingArgs(this.RowIndex, columnIndex, this.Data[columnIndex], value);
            this.ParentTable.OnValueChanging(this, args1);
            if (!args1.Cancel)
            {
                SimpleDataValueChangeArgs args2 = new SimpleDataValueChangeArgs(this.RowIndex, columnIndex, this.Data[columnIndex], args1.ValueNew);
                this.Data[columnIndex] = args1.ValueNew;
                this.ParentTable.OnValueChange(this, args2);
            }
        }
        /// <summary>
        /// Vrací Image vytvořený z dodaného pole byte[].
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        private object _GetAsImage(object value)
        {
            byte[] content = value as byte[];
            if (content == null) return null;
            using (System.IO.MemoryStream ms = new System.IO.MemoryStream(content))
            {
                return System.Drawing.Image.FromStream(ms);
            }
        }
        #endregion
    }
    #endregion
    #region class SimpleDataCellCollection, SimpleDataCell
    /// <summary>
    /// Kolekce vlastností buněk.
    /// K vlastnostem jednotlivých buněk se přistupuje přes indexer this[int columnIndex].
    /// </summary>
    public class SimpleDataCellCollection
    {
        public SimpleDataCellCollection(SimpleDataRow ownerRow)
        {
            this._OwnerRow = ownerRow;
            this._CellDict = new Dictionary<int, SimpleDataCell>();
        }
        /// <summary>
        /// Řádek, do nějž tato kolekce patří
        /// </summary>
        public SimpleDataRow OwnerRow { get { return this._OwnerRow; } }
        private SimpleDataRow _OwnerRow;
        private Dictionary<int, SimpleDataCell> _CellDict;
        /// <summary>
        /// Vrátí vlastnosti jedné konkrétní buňky.
        /// Indexer implicitně zakládá objekty pro buňky, na které se ptáme poprvé.
        /// K chybě dojde, pokud čteme vlastnosti neexistujícího sloupce.
        /// </summary>
        /// <param name="columnName"></param>
        /// <returns></returns>
        public SimpleDataCell this[string columnName]
        {
            get
            {
                int columnIndex = this._OwnerRow.ParentTable.Columns[columnName].ColumnIndex;
                return this[columnIndex];
            }
        }
        /// <summary>
        /// Vrátí vlastnosti jedné konkrétní buňky.
        /// Indexer implicitně zakládá objekty pro buňky, na které se ptáme poprvé.
        /// K chybě dojde, pokud čteme vlastnosti neexistujícího sloupce.
        /// </summary>
        /// <param name="columnIndex"></param>
        /// <returns></returns>
        public SimpleDataCell this[int columnIndex]
        {
            get
            {
                this._CheckColumnIndex(columnIndex);
                SimpleDataCell cell;
                if (!this._CellDict.TryGetValue(columnIndex, out cell))
                {
                    cell = new SimpleDataCell(this, columnIndex);
                    this._CellDict.Add(columnIndex, cell);
                }
                return cell;
            }
        }
        /// <summary>
        /// Vrací příznak, zda řádek obsahuje explicitní data pro daný sloupec.
        /// Indexer this[] dokáže data SimpleDataCell automaticky přidávat, což někdy nemusí být účelné (default = null někdy docela vyhovuje).
        /// K chybě dojde, pokud čteme vlastnosti neexistujícího sloupce.
        /// </summary>
        /// <param name="columnName"></param>
        /// <returns></returns>
        public bool ContainData(string columnName)
        {
            int columnIndex = this._OwnerRow.ParentTable.Columns[columnName].ColumnIndex;
            return this.ContainData(columnIndex);
        }
        /// <summary>
        /// Vrací příznak, zda řádek obsahuje explicitní data pro daný sloupec.
        /// Indexer this[] dokáže data SimpleDataCell automaticky přidávat, což někdy nemusí být účelné (default = null někdy docela vyhovuje).
        /// K chybě dojde, pokud čteme vlastnosti neexistujícího sloupce.
        /// </summary>
        /// <param name="columnIndex"></param>
        /// <returns></returns>
        public bool ContainData(int columnIndex)
        {
            this._CheckColumnIndex(columnIndex);
            return this._CellDict.ContainsKey(columnIndex);
        }
        /// <summary>
        /// Ověří správnost indexu sloupce
        /// </summary>
        /// <param name="columnIndex"></param>
        private void _CheckColumnIndex(int columnIndex)
        {
            if (columnIndex < 0)
                throw new FrxException("Column index must be a non-negative value. Current index = " + columnIndex.ToString() + ".");
            if (columnIndex >= this._OwnerRow.ValueCount)
                throw new FrxException("Column index must be a smaller than row.ValueCount. Current index = " + columnIndex.ToString() + ", ValueCount = " + this._OwnerRow.ValueCount.ToString() + ".");
        }
        /// <summary>
        /// Vytvoří a vrátí Clone
        /// </summary>
        /// <returns></returns>
        internal SimpleDataCellCollection Clone(SimpleDataRow ownerRow)
        {
            SimpleDataCellCollection clone = new SimpleDataCellCollection(ownerRow);
            foreach (var item in this._CellDict)
                clone._CellDict.Add(item.Key, item.Value.Clone(this, item.Key));
            return clone;
        }
    }
    /// <summary>
    /// Souhrn vlastností jedné konkrétní buňky
    /// </summary>
    public class SimpleDataCell
    {
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="owner"></param>
        /// <param name="columnIndex"></param>
        public SimpleDataCell(SimpleDataCellCollection owner, int columnIndex)
        {
            this._Owner = owner;
            this._ColumnIndex = columnIndex;
        }
        private SimpleDataCellCollection _Owner;
        private int _ColumnIndex;
        /// <summary>
        /// Hodnota v této buňce
        /// </summary>
        public object Value
        {
            get { return this._Owner.OwnerRow[this._ColumnIndex]; }
            set { this._Owner.OwnerRow[this._ColumnIndex] = value; }
        }
        /// <summary>
        /// Příznak, zda buňka má tooltip
        /// </summary>
        public bool HasToolTipText { get { return !String.IsNullOrEmpty(this.ToolTipText); } }
        /// <summary>
        /// ToolTipText v této buňce
        /// </summary>
        public string ToolTipText { get; set; }
        /// <summary>
        /// Je buňka editovatelná?
        /// Zde může být null, pak se převezme z řádku, ze sloupce nebo tabulky.
        /// </summary>
        public bool? Enabled { get; set; }
        /// <summary>
        /// Styl buňky.
        /// Zde může být null, pak se převezme z řádku, ze sloupce nebo tabulky.
        /// </summary>
        public SimpleDataStyle CellStyle { get; set; }
        /// <summary>
        /// Vrátí klon sebe sama (obsahuje shodná data), ale je navázaný na specifikovaný objekt a sloupec.
        /// </summary>
        /// <param name="owner"></param>
        /// <param name="columnIndex"></param>
        /// <returns></returns>
        public SimpleDataCell Clone(SimpleDataCellCollection owner, int columnIndex)
        {
            SimpleDataCell clone = (SimpleDataCell)this.MemberwiseClone();
            clone._Owner = owner;
            clone._ColumnIndex = columnIndex;
            return clone;
        }
    }
    #endregion
    #region class SimpleDataFormatter
    /// <summary>
    /// SimpleDataFormatter : zajišťuje formátování dat pro sloupec / buňku.
    /// Instance je uložena v Columnu, a v případě potřeby i v buňce (Cell), pokud má odlišné formátování než sloupec.
    /// Formatter pracuje s editačním stylem Greenu, nebo dovoluje explicitně zadat masku nebo soupis hodnot (objekt IEditStyleList)
    /// </summary>
    public class SimpleDataFormatter
    {
        #region Editační styl, editační maska
        /// <summary>
        /// Editační styl načtený z Greenu.
        /// </summary>
        public EditStyleInfo EditStyle
        {
            get { return this._EditStyle; }
            set
            {
                this._EditStyle = value;
                this._EditStyleList = null;
                this._EditMask = null;
                if (value == null)
                    return;

                this._ProcessEditStyle(value);
            }
        }
        /// <summary>
        /// Položky nabídky editačního stylu.
        /// Ve formě IEditStyleList obsahují Value a DisplayText.
        /// </summary>
        public IEditStyleList EditStyleList
        {
            get { return this._EditStyleList; }
            set
            {
                this._EditStyleList = value;
                this._EditStyle = null;
                this._EditStyleValues = null;
                this._EditMask = null;
                this._ProcessEditStyleList(value.Items);
            }
        }
        /// <summary>
        /// Položky nabídky editačního stylu.
        /// Ve formě stringu jsou obsaženy pouze DisplayText, které se stávají současně i Value (=do tabulky se ukládá zobrazovaný text).
        /// Jde tedy o valuovaný atribut.
        /// String může obsahovat oddělovače položek Cr, Lf, Tab, Středník, Čárka (zde uvedené pořadí deklaruje prioritu).
        /// Jako skutečně použitý oddělovač se bere ten s nejvyšší prioritou, který je ve stringu přítomen.
        /// </summary>
        public string EditStyleValues
        {
            get { return this._EditStyleValues; }
            set
            {
                this._EditStyleValues = value;
                this._EditStyle = null;
                this._EditStyleList = null;
                this._EditMask = null;
                this._ProcessEditStyleValues(value);
            }
        }
        /// <summary>
        /// Maska editace
        /// </summary>
        public string EditMask
        {
            get { return this._EditMask; }
            set
            {
                this._EditMask = value;
                this._EditStyle = null;
                this._EditStyleList = null;
                this._EditStyleValues = null;
                if (!String.IsNullOrEmpty(value))
                    this._ProcessEditStyleMask(value);
            }
        }
        /// <summary>
        /// Styl, jakým se mají renderovat data tohoto sloupce (druh Controlu)
        /// </summary>
        public SimpleDataRenderAsType RenderAs
        { get; set; }
        private EditStyleInfo _EditStyle;
        private IEditStyleList _EditStyleList;
        private string _EditStyleValues;
        private string _EditMask;
        #endregion
        #region Zpracování editačních stylů
        /// <summary>
        /// Zpracuje editační styl
        /// </summary>
        /// <param name="editStyle"></param>
        private void _ProcessEditStyle(EditStyleInfo editStyle)
        {
            switch (editStyle.InputType)
            {
                case EditStyleInputType.EditMask:
                    if (!String.IsNullOrEmpty(editStyle.MaskNet))
                    {
                        this._EditMask = editStyle.MaskNet;
                        this._ProcessEditStyleMask(editStyle.MaskNet);
                    }
                    this.RenderAs = SimpleDataRenderAsType.TextBox;
                    break;

                case EditStyleInputType.CheckBox:
                    this.RenderAs = SimpleDataRenderAsType.CheckBox;
                    break;

                case EditStyleInputType.RadioButton:
                case EditStyleInputType.DropDownListbox:
                    if (editStyle.HasItems)
                    {
                        this._EditStyleList = (IEditStyleList)editStyle;
                        this._ProcessEditStyleList(editStyle.ItemListSorted);
                    }
                    this.RenderAs = SimpleDataRenderAsType.ComboBox;
                    break;

                default:
                    this.RenderAs = SimpleDataRenderAsType.TextBox;
                    break;

            }
        }
        /// <summary>
        /// Uloží do sebe seznam položek editačního stylu.
        /// Vytvoří si index.
        /// </summary>
        /// <param name="list"></param>
        private void _ProcessEditStyleList(IEnumerable<IEditStyleItem> list)
        {
            this._EditStyleItems = new List<IEditStyleItem>(list);
            this._EditStyleIndex = new IndexUnique<IComparable, IEditStyleItem>(i => i.Value, list);
            this.RenderAs = SimpleDataRenderAsType.ComboBox;
        }
        /// <summary>
        /// Uloží do sebe seznam položek editačního stylu, který vytvoří z dodaného seznamu položek.
        /// Vytvoří si index.
        /// </summary>
        /// <param name="value"></param>
        private void _ProcessEditStyleValues(string value)
        {
            // Vyhledám oddělovač: první z řady povolených oddělovačů:
            char[] delimiters = new char[] { '\r', '\n', '\t', ';', ',' };
            char delimiter = (char)0;
            if (value != null)
            {
                foreach (char d in delimiters)
                {
                    if (value.Contains(d))
                    {
                        delimiter = d;
                        break;
                    }
                }
            }

            // Rozdělím text v místě oddělovače, a vytvořím list položek, kde Key = Text, a pořadí odpovídá pořadí v textu:
            List<EditStyleItem> list = new List<EditStyleItem>();
            if (delimiter != (char)0)
            {
                string text = value.Trim();
                if (delimiter == '\r')    // Pokud je zvolen delimiter CR, pak odstraním LF:
                    text = text.Replace("\n", "");

                int position = 0;

                // Pokud na první pozici je separátor, značí to fakt, že první položka editačního stylu má být Empty (protože běžně se všechny empty položky ignorují):
                if (value.Length > 0 && value[0] == delimiter)
                    list.Add(new EditStyleItem("", position++, ""));

                string[] items = text.Split(new char[] { delimiter }, StringSplitOptions.RemoveEmptyEntries);
                for (int i = 0; i < items.Length; i++)
                {
                    string item = items[i].Trim();
                    list.Add(new EditStyleItem(item, position++, item));
                }
            }

            // Dále pokračuji cestou IEnumerable<IEditStyleItem>:
            this._ProcessEditStyleList(list);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="mask"></param>
        private void _ProcessEditStyleMask(string mask)
        {
            this._EditMask = mask;
            this.RenderAs = SimpleDataRenderAsType.TextBox;
        }
        private List<IEditStyleItem> _EditStyleItems;
        private IndexUnique<IComparable, IEditStyleItem> _EditStyleIndex;
        #endregion
        #region Převod hodnoty datové na vizuální a naopak
        /// <summary>
        /// Obsahuje setříděný seznam hodnot pro zobrazení v ComboBoxu
        /// </summary>
        public IEnumerable<IEditStyleItem> EditStyleItems { get { return this._EditStyleItems; } }
        public string GetDisplayValue(object value)
        {
            if (value == null) return null;
            
            IComparable key = null;
            if (value is IComparable)
                key = value as IComparable;
            else
                key = value.ToString();

            if (this._EditStyleIndex != null)
            {
                IEditStyleItem item;
                if (this._EditStyleIndex.TryGetValue(key, out item))
                    return item.DisplayText;
            }

            return value.ToString();
        }

        public object GetDataValue(object value)
        {
            if (value == null) return null;

            string text = (value is string ? (string)value : value.ToString());
            IEditStyleItem item = this._EditStyleItems.FirstOrDefault(i => i.DisplayText == text);
            if (item == null) return value;

            return item.Value;
        }
        #endregion
    }
    #endregion
    #region class SimpleDataStyle, SimpleDataStyleOne, enum SimpleDataStyleState, SimpleDataStyleEffect
    /// <summary>
    /// SimpleDataStyle : Vizuální styl.
    /// Styl může mít tabulka, sloupec, řádek, buňka.
    /// Styl obsahuje šest jednotkových definic, pro prvek (Standard, HotMouse, ActiveRow a totéž za stavu Selected).
    /// Styl obsahuje i definici fontu.
    /// </summary>
    public class SimpleDataStyle
    {
        #region Konstrukce, instanční property
        /// <summary>
        /// Konstruktor
        /// </summary>
        public SimpleDataStyle()
        {
            this._Sets = new Dictionary<SimpleDataStyleState, SimpleDataStyleOne>();
        }
        /// <summary>
        /// Sada stylů podle stavu prvku
        /// </summary>
        private Dictionary<SimpleDataStyleState, SimpleDataStyleOne> _Sets;
        /// <summary>Písmo</summary>
        public Font Font { get; set; }
        /// <summary>
        /// Klon = nový objekt obsahující stejná data, ale izolovaná od svého původce.
        /// </summary>
        /// <returns></returns>
        public SimpleDataStyle Clone()
        {
            SimpleDataStyle clone = new SimpleDataStyle();
            foreach (KeyValuePair<SimpleDataStyleState, SimpleDataStyleOne> item in this._Sets)
                clone._Sets.Add(item.Key, item.Value);
            if (this.Font != null)
                clone.Font = (Font)this.Font.Clone();
            return clone;
        }
        #endregion
        #region Indexer - čtení a zápis dat
        /// <summary>
        /// Přístup k datům stylu
        /// </summary>
        /// <param name="state"></param>
        /// <returns></returns>
        public SimpleDataStyleOne this[SimpleDataStyleState state]
        {
            get
            {
                SimpleDataStyleOne style; 
                if (this._Sets.TryGetValue(state, out style)) return style;
                return null;
            }
            set
            {
                if (this._Sets.ContainsKey(state))
                    this._Sets[state] = value;
                else
                    this._Sets.Add(state, value);
            }
        }
        #endregion
        #region Statický konstruktor defaultních stylů
        /// <summary>
        /// Obsahuje (vždy vygeneruje new) objekt popisující výchozí styl
        /// </summary>
        public static SimpleDataStyle Default
        {
            get
            {
                SimpleDataStyle result = new SimpleDataStyle();

                result[SimpleDataStyleState.Standard] = new SimpleDataStyleOne(Color.Lavender, SimpleDataStyleEffect.Outset, Color.Black);
                result[SimpleDataStyleState.HotMouse] = new SimpleDataStyleOne(Color.LavenderBlush, SimpleDataStyleEffect.Outset, Color.Black);
                result[SimpleDataStyleState.ActiveRow] = new SimpleDataStyleOne(Color.LightSkyBlue, SimpleDataStyleEffect.Inset, Color.Black);
                result[SimpleDataStyleState.ActiveRowHotMouse] = new SimpleDataStyleOne(Color.LightSkyBlue, SimpleDataStyleEffect.Inset, Color.Black);
                result[SimpleDataStyleState.Selected] = new SimpleDataStyleOne(Color.FromArgb(247, 247, 207), SimpleDataStyleEffect.Inset, Color.Black);
                result[SimpleDataStyleState.SelectedHotMouse] = new SimpleDataStyleOne(Color.BlanchedAlmond, SimpleDataStyleEffect.Inset, Color.Black);
                result[SimpleDataStyleState.SelectedActiveRow ] = new SimpleDataStyleOne(Color.DeepSkyBlue, SimpleDataStyleEffect.Inset, Color.White);
                result[SimpleDataStyleState.SelectedActiveRowHotMouse] = new SimpleDataStyleOne(Color.DeepSkyBlue, SimpleDataStyleEffect.Inset, Color.White);

                result[SimpleDataStyleState.Disabled] = new SimpleDataStyleOne(Color.FromArgb(212, 215, 220), SimpleDataStyleEffect.Outset, 0.3f, Color.Black);
                result[SimpleDataStyleState.DisabledHotMouse] = new SimpleDataStyleOne(Color.LightSteelBlue, SimpleDataStyleEffect.Outset, 0.3f, Color.Black);
                result[SimpleDataStyleState.DisabledActiveRow] = new SimpleDataStyleOne(Color.LightSkyBlue, SimpleDataStyleEffect.Inset, Color.Black);
                result[SimpleDataStyleState.DisabledActiveRowHotMouse] = new SimpleDataStyleOne(Color.LightSkyBlue, SimpleDataStyleEffect.Inset, Color.Black);
                result[SimpleDataStyleState.DisabledSelected] = new SimpleDataStyleOne(Color.FromArgb(247, 247, 207), SimpleDataStyleEffect.Inset, 0.6f, Color.Black);
                result[SimpleDataStyleState.DisabledSelectedHotMouse] = new SimpleDataStyleOne(Color.LightSteelBlue, SimpleDataStyleEffect.Inset, 0.6f, Color.Black);
                result[SimpleDataStyleState.DisabledSelectedActiveRow] = new SimpleDataStyleOne(Color.DeepSkyBlue, SimpleDataStyleEffect.Inset, Color.Black);
                result[SimpleDataStyleState.DisabledSelectedActiveRowHotMouse] = new SimpleDataStyleOne(Color.DeepSkyBlue, SimpleDataStyleEffect.Inset, Color.Black);

                result.Font = SystemFonts.DefaultFont;
                return result;
            }
        }
        #endregion
    }
    /// <summary>
    /// Definice jedné barvy pozadí + popředí + stylu
    /// </summary>
    public class SimpleDataStyleOne
    {
        #region Konstrukce, property
        public SimpleDataStyleOne(Color backColor, SimpleDataStyleEffect effectType, Color foreColor)
        {
            this.BackColor = backColor;
            this.EffectShiftRatio = 0.8f;
            this.EffectType = effectType;
            this.ForeColor = foreColor;
            this.UnderlineLeft = Color.MidnightBlue;
            this.UnderlineRight = Color.CornflowerBlue;
        }
        public SimpleDataStyleOne(Color backColor, SimpleDataStyleEffect effectType, float effectShiftRatio, Color foreColor)
        {
            this.BackColor = backColor;
            this.EffectShiftRatio = effectShiftRatio;
            this.EffectType = effectType;
            this.ForeColor = foreColor;
            this.UnderlineLeft = Color.MidnightBlue;
            this.UnderlineRight = Color.CornflowerBlue;
        }
        /// <summary>Barva pozadí sytější (druhá barva pro styl Outset a Inset se určí posunem o EffectShift směrem k barvě White)</summary>
        public Color BackColor { get; set; }
        /// <summary>Posun doplňkové barvy pro styl Outset a Inset, směrem k barvě White. 0=žádný posun, 1.0f=čistě bílá, hodnoty mezi tím = "ColorMorphing".</summary>
        public float EffectShiftRatio { get; set; }
        /// <summary>Barevný efekt barvy pozadí</summary>
        public SimpleDataStyleEffect EffectType { get; set; }
        /// <summary>Barva popředí</summary>
        public Color ForeColor { get; set; }
        /// <summary>Barva podtržení buňky, vlevo</summary>
        public Color UnderlineLeft { get; set; }
        /// <summary>Barva podtržení buňky, vpravo</summary>
        public Color UnderlineRight { get; set; }
        /// <summary>
        /// Klon = nový objekt obsahující stejná data, ale izolovaná od svého původce.
        /// </summary>
        /// <returns></returns>
        public SimpleDataStyleOne Clone()
        {
            return (SimpleDataStyleOne)this.MemberwiseClone();
        }
        #endregion
        #region Fyzická podpora pro vykreslování buňky
        /// <summary>
        /// Vrátí štětec pro vymalování pozadí buňky
        /// </summary>
        /// <param name="area"></param>
        /// <param name="joinedCell"></param>
        /// <returns></returns>
        public Brush GetBrush(Rectangle area, bool joinedCell)
        {
            if (this.EffectType == SimpleDataStyleEffect.Flat)
                return new SolidBrush(this.BackColor);

            Color color1 = this.BackColor;
            Color color2 = color1.Morph(Color.White, this.EffectShiftRatio);
            float angle = (this.EffectType == SimpleDataStyleEffect.Outset ? -90f : 90f);
            if (joinedCell)
                angle = 0f; // -90f;

            return new System.Drawing.Drawing2D.LinearGradientBrush(area, color1, color2, angle);
        }
        #endregion
    }
    /// <summary>
    /// Stav buňky z hlediska vizuálního stylu
    /// </summary>
    [Flags]
    public enum SimpleDataStyleState
    {
        /// <summary>0: Běžný stav, bez myši, není Disabled, není Selected, není Active</summary>
        Standard = 0,
        /// <summary>1: Prvek pod myší</summary>
        HotMouse = 1,
        /// <summary>2: Prvek v aktivním řádku (kde je kurzor)</summary>
        ActiveRow = 2,
        /// <summary>3: Prvek pod myší + v aktivním řádku (kde je kurzor)</summary>
        ActiveRowHotMouse = ActiveRow | HotMouse,
        /// <summary>4: Prvek vybraný (ke kopírování)</summary>
        Selected = 4,
        /// <summary>5: Prvek pod myší + vybraný (ke kopírování)</summary>
        SelectedHotMouse = Selected | HotMouse,
        /// <summary>6: Prvek vybraný (ke kopírování) + v aktivním řádku (kde je kurzor)</summary>
        SelectedActiveRow = Selected | ActiveRow,
        /// <summary>7: Prvek pod myší + vybraný (ke kopírování) + v aktivním řádku (kde je kurzor)</summary>
        SelectedActiveRowHotMouse = Selected | ActiveRow | HotMouse,
        /// <summary>8: Prvek bez možnosti editace</summary>
        Disabled = 8,
        /// <summary>9: Prvek pod myší + bez možnosti editace</summary>
        DisabledHotMouse = Disabled | HotMouse,
        /// <summary>10: Prvek v aktivním řádku (kde je kurzor) + bez možnosti editace</summary>
        DisabledActiveRow = Disabled | ActiveRow,
        /// <summary>11: Prvek pod myší + v aktivním řádku (kde je kurzor) + bez možnosti editace</summary>
        DisabledActiveRowHotMouse = Disabled | ActiveRow | HotMouse,
        /// <summary>12: Prvek vybraný (ke kopírování) + bez možnosti editace</summary>
        DisabledSelected = Disabled | Selected,
        /// <summary>13: Prvek pod myší + vybraný (ke kopírování) + bez možnosti editace</summary>
        DisabledSelectedHotMouse = Disabled | Selected | HotMouse,
        /// <summary>14: Prvek vybraný (ke kopírování) + v aktivním řádku (kde je kurzor) + bez možnosti editace</summary>
        DisabledSelectedActiveRow = Disabled | Selected | ActiveRow,
        /// <summary>15: Prvek pod myší + vybraný (ke kopírování) + v aktivním řádku (kde je kurzor) + bez možnosti editace</summary>
        DisabledSelectedActiveRowHotMouse = Disabled | Selected | ActiveRow | HotMouse
    }
    /// <summary>
    /// Styl efektu barvy pozadí
    /// </summary>
    public enum SimpleDataStyleEffect
    {
        /// <summary>Plochý styl, prosté vyplnění barvou BackColor</summary>
        Flat,
        /// <summary>Styl vystouplý ven = horní hrana je světlejší, dolní hrana je barvou BackColor</summary>
        Outset,
        /// <summary>Styl zamáčknutý dovnitř = horní hrana je barvou BackColor, dolní hrana je světlejší</summary>
        Inset
    }
    #endregion
    #region class SimpleDataArgs a její potomstvo - argumenty pro eventy. Odpovídající delegáti.
    /// <summary>
    /// Základní třída pro argumenty
    /// </summary>
    public class SimpleDataArgs : EventArgs
    { }
    /// <summary>
    /// Třída pro událost Změna hodnoty
    /// </summary>
    public class SimpleDataValueChangeArgs : EventArgs
    {
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="rowIndex"></param>
        /// <param name="columnIndex"></param>
        /// <param name="valueOld"></param>
        /// <param name="valueNew"></param>
        public SimpleDataValueChangeArgs(int rowIndex, int columnIndex, object valueOld, object valueNew)
        {
            this.RowIndex = rowIndex;
            this.ColumnIndex = columnIndex;
            this.ValueOld = valueOld;
            this.ValueNew = valueNew;
        }
        /// <summary>
        /// Index řádku, kde došlo ke změně
        /// </summary>
        public int RowIndex { get; private set; }
        /// <summary>
        /// Index sloupce, kde došlo ke změně
        /// </summary>
        public int ColumnIndex { get; private set; }
        /// <summary>
        /// Původní hodnota před změnou
        /// </summary>
        public object ValueOld { get; private set; }
        /// <summary>
        /// Nová hodnota po změně
        /// </summary>
        public object ValueNew { get; private set; }
    }
    /// <summary>
    /// Předpis handleru pro události o provedené změně
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="args"></param>
    public delegate void SimpleDataValueChangeHandler(object sender, SimpleDataValueChangeArgs args);
    /// <summary>
    /// Třída pro událost Změna hodnoty
    /// </summary>
    public class SimpleDataValueChangingArgs : SimpleDataValueChangeArgs
    {
        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="rowIndex"></param>
        /// <param name="columnIndex"></param>
        /// <param name="valueOld"></param>
        /// <param name="valueNew"></param>
        public SimpleDataValueChangingArgs(int rowIndex, int columnIndex, object valueOld, object valueNew)
            : base(rowIndex, columnIndex, valueOld, valueNew)
        {
            this.ValueNew = valueNew;
            this.Cancel = false;
        }
        /// <summary>
        /// Nová hodnota, která se bude do prvku vkládat.
        /// V události Changing je možno ji změnit.
        /// </summary>
        public new object ValueNew { get; set; }
        /// <summary>
        /// Pokud má dojít k odmítnutí, nastaví se true.
        /// </summary>
        public bool Cancel { get; set; }
    }
    /// <summary>
    /// Předpis handleru pro události o probíhající změně
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="args"></param>
    public delegate void SimpleDataValueChangingHandler(object sender, SimpleDataValueChangingArgs args);
    #endregion
    #region Testy
    internal static class SimpleDataTableTest
    {
        internal static void TestMemory()
        {
            string[] selects = new string[]
            {
                "SELECT TOP 500 * FROM lcs.subjekty",
                "select top 5000 * from lcs.product_order order by cislo_subjektu",
                "select * from lcs.objekt_ucetnipohyb",
                "SELECT * FROM lcs.externi_soubor_data where cislo_nonsubjektu = 4"
            };

            // TestMemoryDataTable(selects);
            TestMemorySimple(selects);

            // Co se týká rychlosti a spotřeby paměti, 
            //    na vzorku dat (3 tabulky: 50 rows * 11 columns + 15 rows * 55 columns + 26117 rows * 57 columns = 1,490,044 values):
            // 1) System.Data.DataTable : spotřeba paměti 35 MB, rychlost 1,1 sekundy
            // 2) SimpleData            : spotřeba paměti 25 MB, rychlost 0,5 sekundy
            // Poznámka: kopie dat do TXT souboru zabere cca 9 MB.
        }
        static void TestMemoryDataTable(string[] selects)
        {
            MessageBox.Show("Before DataTable");

            System.Diagnostics.Process i = System.Diagnostics.Process.GetCurrentProcess();
            Int64 sizeB1 = i.PagedMemorySize64;
            Int64 sizeB2 = i.PrivateMemorySize64;

            Asol.Reporting.Support.Steward.ConnectToProfile(Asol.Reporting.Support.Steward.AppConfig.DbProfileList[0]);

            System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
            sw.Start();

            int rows = 0;
            List<System.Data.DataTable> tables = new List<System.Data.DataTable>();
            foreach (string select in selects)
            {
                System.Data.DataTable table = Asol.Reporting.Support.Sql.Db_Layer.GetDataTable(select);
                rows += table.Rows.Count;
                tables.Add(table);
            }

            sw.Stop();
            decimal time = (decimal)sw.ElapsedTicks / (decimal)System.Diagnostics.Stopwatch.Frequency;

            i = System.Diagnostics.Process.GetCurrentProcess();
            Int64 sizeA1 = i.PagedMemorySize64;
            Int64 sizeA2 = i.PrivateMemorySize64;

            MessageBox.Show("After. DeltaMemory=" + (sizeA1 - sizeB1).ToString() + ".  RowCount=" + rows.ToString() + ".  Time = " + time.ToString("####0.000") + " sec");
            GC.Collect();
            GC.Collect();
        }
        static void TestMemorySimple(string[] selects)
        {
            MessageBox.Show("Before TableSimple");


            System.Diagnostics.Process i = System.Diagnostics.Process.GetCurrentProcess();
            Int64 sizeB1 = i.PagedMemorySize64;
            Int64 sizeB2 = i.PrivateMemorySize64;

            Asol.Reporting.Support.Steward.ConnectToProfile(Asol.Reporting.Support.Steward.AppConfig.DbProfileList[0]);

            System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
            sw.Start();

            int rows = 0;
            int items = 0;
            List<SimpleDataTable> tables = new List<SimpleDataTable>();
            foreach (string select in selects)
            {
                SimpleDataTable table = SimpleDataTable.Load(select);
                tables.Add(table);
                rows += table.RowCount;
                items += table.RowCount * table.ColumnCount;
            }

            sw.Stop();
            decimal time = (decimal)sw.ElapsedTicks / (decimal)System.Diagnostics.Stopwatch.Frequency;

            SimpleDataTable table1 = tables[tables.Count - 1];
            foreach (SimpleDataRow row in table1.Rows)
            {
                foreach (SimpleDataColumn col in table1.Columns)
                {
                    object value = row.Get<object>(col.ColumnIndex);
                    if (value is byte[])
                    {
                        System.Drawing.Image img = row.Get<System.Drawing.Image>(col.ColumnIndex);
                    }
                }
                break;
            }


            i = System.Diagnostics.Process.GetCurrentProcess();
            Int64 sizeA1 = i.PagedMemorySize64;
            Int64 sizeA2 = i.PrivateMemorySize64;

            MessageBox.Show("After. DeltaMemory=" + (sizeA1 - sizeB1).ToString() + ".  RowCount=" + rows.ToString() + ".   ItemCount=" + items.ToString() + "; time=" + time.ToString("####0.000") + " sec");
            GC.Collect();
            GC.Collect();
        }
    }
    #endregion
}
